home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1843 / 1843.xpi / content / firebug / dom.js < prev    next >
Text File  |  2010-01-15  |  62KB  |  2,066 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const Cc = Components.classes;
  9. const Ci = Components.interfaces;
  10. const jsdIStackFrame = Ci.jsdIStackFrame;
  11.  
  12. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  13.  
  14. const insertSliceSize = 18;
  15. const insertInterval = 40;
  16.  
  17. const rxIdentifier = /^[$_A-Za-z][$_A-Za-z0-9]*$/
  18.  
  19. const ignoreVars =
  20. {
  21.     "__firebug__": 1,
  22.     "eval": 1,
  23.  
  24.     // We are forced to ignore Java-related variables, because
  25.     // trying to access them causes browser freeze
  26.     "java": 1,
  27.     "sun": 1,
  28.     "Packages": 1,
  29.     "JavaArray": 1,
  30.     "JavaMember": 1,
  31.     "JavaObject": 1,
  32.     "JavaClass": 1,
  33.     "JavaPackage": 1,
  34.     // internal firebug things
  35.     "_firebug": 1,
  36.     "_FirebugConsole": 1,
  37.     "_FirebugCommandLine": 1,
  38.     "loadFirebugConsole": 1,
  39.     "_getFirebugConsoleElement": 1,
  40. };
  41.  
  42. // ************************************************************************************************
  43.  
  44. Firebug.DOMModule = extend(Firebug.Module,
  45. {
  46.     initialize: function(prefDomain, prefNames)
  47.     {
  48.         Firebug.Module.initialize.apply(this, arguments);
  49.         Firebug.Debugger.addListener(this.DebuggerListener);
  50.     },
  51.  
  52.     initContext: function(context, persistedState)
  53.     {
  54.         Firebug.Module.initContext.apply(this, arguments);
  55.         context.dom = {breakpoints: new DOMBreakpointGroup()};
  56.     },
  57.  
  58.     loadedContext: function(context, persistedState)
  59.     {
  60.         context.dom.breakpoints.load(context);
  61.     },
  62.  
  63.     destroyContext: function(context, persistedState)
  64.     {
  65.         Firebug.Module.destroyContext.apply(this, arguments);
  66.  
  67.         context.dom.breakpoints.store(context);
  68.     },
  69.  
  70.     shutdown: function()
  71.     {
  72.         Firebug.Module.shutdown.apply(this, arguments);
  73.         Firebug.Debugger.removeListener(this.DebuggerListener);
  74.     },
  75. });
  76.  
  77. // ************************************************************************************************
  78.  
  79. const WatchRowTag =
  80.     TR({"class": "watchNewRow", level: 0},
  81.         TD({"class": "watchEditCell", colspan: 3},
  82.             DIV({"class": "watchEditBox a11yFocusNoTab", role: "button", 'tabindex' : '0',
  83.                 'aria-label' : $STR('a11y.labels.press enter to add new watch expression')},
  84.                     $STR("NewWatch")
  85.             )
  86.         )
  87.     );
  88.  
  89. const SizerRow =
  90.     TR({role : 'presentation'},
  91.         TD(),
  92.         TD({width: "30%"}),
  93.         TD({width: "70%"})
  94.     );
  95.  
  96. const DirTablePlate = domplate(Firebug.Rep,
  97. {
  98.     memberRowTag:
  99.         TR({"class": "memberRow $member.open $member.type\\Row", _domObject: "$member",
  100.             $hasChildren: "$member.hasChildren",
  101.             role: "presentation",
  102.             level: "$member.level",
  103.             breakable: "$member.breakable",
  104.             breakpoint: "$member.breakpoint",
  105.             disabledBreakpoint: "$member.disabledBreakpoint"},
  106.             TD({"class": "memberHeaderCell"},
  107.                DIV({"class": "sourceLine memberRowHeader", onclick: "$onClickRowHeader"},
  108.                     " "
  109.                )
  110.             ),
  111.             TD({"class": "memberLabelCell", style: "padding-left: $member.indent\\px",
  112.                 role: 'presentation'},
  113.                 DIV({"class": "memberLabel $member.type\\Label"},
  114.                     SPAN({"class": "memberLabelPrefix"}, "$member.prefix"),
  115.                     SPAN("$member.name")
  116.                 )
  117.             ),
  118.             TD({"class": "memberValueCell", role : 'presentation'},
  119.                 TAG("$member.tag", {object: "$member.value"})
  120.             )
  121.         ),
  122.  
  123.     tag:
  124.         TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0, onclick: "$onClick",
  125.             role: "tree", 'aria-label': $STR('aria.labels.dom properties')},
  126.             TBODY({role: 'presentation'},
  127.                 SizerRow,
  128.                 FOR("member", "$object|memberIterator",
  129.                     TAG("$memberRowTag", {member: "$member"})
  130.                 )
  131.             )
  132.         ),
  133.  
  134.     watchTag:
  135.         TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0,
  136.                _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", role : 'tree'},
  137.             TBODY({role : 'presentation'},
  138.                 SizerRow,
  139.                 WatchRowTag
  140.             )
  141.         ),
  142.  
  143.     tableTag:
  144.         TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0,
  145.             _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick",
  146.             role: 'tree', 'aria-label': 'DOM properties'},
  147.             TBODY({role : 'presentation'},
  148.                 SizerRow
  149.             )
  150.         ),
  151.  
  152.     rowTag:
  153.         FOR("member", "$members",
  154.             TAG("$memberRowTag", {member: "$member"})
  155.         ),
  156.  
  157.     memberIterator: function(object, level)
  158.     {
  159.         return Firebug.DOMBasePanel.prototype.getMembers(object, level, this.context);
  160.     },
  161.  
  162.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  163.  
  164.     onClick: function(event)
  165.     {
  166.         if (!isLeftClick(event))
  167.             return;
  168.  
  169.         var row = getAncestorByClass(event.target, "memberRow");
  170.         var label = getAncestorByClass(event.target, "memberLabel");
  171.         var valueCell = row.getElementsByClassName("memberValueCell").item(0);
  172.         var object = Firebug.getRepObject(event.target);
  173.         var target = row.lastChild.firstChild;
  174.         var isString = hasClass(target,"objectBox-string");
  175.         var inValueCell = event.target == valueCell || event.target == target;
  176.  
  177.         if (label && hasClass(row, "hasChildren") && !(isString && inValueCell))
  178.         {
  179.             var row = label.parentNode.parentNode;
  180.             this.toggleRow(row);
  181.         }
  182.         else
  183.         {
  184.             if (typeof(object) == "function")
  185.             {
  186.                 Firebug.chrome.select(object, "script");
  187.                 cancelEvent(event);
  188.             }
  189.             else if (event.detail == 2 && !object)
  190.             {
  191.                 var panel = row.parentNode.parentNode.domPanel;
  192.                 if (panel)
  193.                 {
  194.                     var rowValue = panel.getRowPropertyValue(row);
  195.                     if (typeof(rowValue) == "boolean")
  196.                         panel.setPropertyValue(row, !rowValue);
  197.                     else
  198.                         panel.editProperty(row);
  199.  
  200.                     cancelEvent(event);
  201.                 }
  202.             }
  203.         }
  204.     },
  205.  
  206.     toggleRow: function(row)
  207.     {
  208.         var level = parseInt(row.getAttribute("level"));
  209.         var toggles = row.parentNode.parentNode.toggles;
  210.  
  211.         var panel = row.parentNode.parentNode.domPanel;
  212.         var target = row.lastChild.firstChild;
  213.         var isString = hasClass(target,"objectBox-string");
  214.  
  215.         if (hasClass(row, "opened"))
  216.         {
  217.             removeClass(row, "opened");
  218.  
  219.             if (isString)
  220.             {
  221.                 var rowValue = panel.getRowPropertyValue(row);
  222.                 row.lastChild.firstChild.textContent = '"' + cropMultipleLines(rowValue) + '"';
  223.             }
  224.             else
  225.             {
  226.                 if (toggles)
  227.                 {
  228.                     var path = getPath(row);
  229.  
  230.                     // Remove the path from the toggle tree
  231.                     for (var i = 0; i < path.length; ++i)
  232.                     {
  233.                         if (i == path.length-1)
  234.                             delete toggles[path[i]];
  235.                         else
  236.                             toggles = toggles[path[i]];
  237.                     }
  238.                 }
  239.  
  240.                 var rowTag = this.rowTag;
  241.                 var tbody = row.parentNode;
  242.  
  243.                 setTimeout(function()
  244.                 {
  245.                     for (var firstRow = row.nextSibling; firstRow; firstRow = row.nextSibling)
  246.                     {
  247.                         if (parseInt(firstRow.getAttribute("level")) <= level)
  248.                             break;
  249.  
  250.                         tbody.removeChild(firstRow);
  251.                     }
  252.                 }, row.insertTimeout ? row.insertTimeout : 0);
  253.             }
  254.         }
  255.         else
  256.         {
  257.             setClass(row, "opened");
  258.             if (isString)
  259.             {
  260.                 var rowValue = panel.getRowPropertyValue(row);
  261.                 row.lastChild.firstChild.textContent = '"' + rowValue + '"';
  262.             }
  263.             else
  264.             {
  265.  
  266.                 if (toggles)
  267.                 {
  268.                     var path = getPath(row);
  269.  
  270.                     // Mark the path in the toggle tree
  271.                     for (var i = 0; i < path.length; ++i)
  272.                     {
  273.                         var name = path[i];
  274.                         if (toggles.hasOwnProperty(name))
  275.                             toggles = toggles[name];
  276.                         else
  277.                             toggles = toggles[name] = {};
  278.                     }
  279.                 }
  280.  
  281.                 var context = panel ? panel.context : null;
  282.                 var members = Firebug.DOMBasePanel.prototype.getMembers(target.repObject, level+1, context);
  283.  
  284.                 var rowTag = this.rowTag;
  285.                 var lastRow = row;
  286.  
  287.                 var delay = 0;
  288.                 var setSize = members.length;
  289.                 var rowCount = 1;
  290.                 while (members.length)
  291.                 {
  292.                     setTimeout(function(slice, isLast)
  293.                     {
  294.                         if (lastRow.parentNode)
  295.                         {
  296.                             var result = rowTag.insertRows({members: slice}, lastRow);
  297.                             lastRow = result[1];
  298.                             dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [null, result, rowCount, setSize]);
  299.                             rowCount += insertSliceSize;
  300.                         }
  301.                         if (isLast)
  302.                             delete row.insertTimeout;
  303.                     }, delay, members.splice(0, insertSliceSize), !members.length);
  304.  
  305.                     delay += insertInterval;
  306.                 }
  307.  
  308.                 row.insertTimeout = delay;
  309.             }
  310.         }
  311.     },
  312.  
  313.     onClickRowHeader: function(event)
  314.     {
  315.         cancelEvent(event);
  316.  
  317.         var rowHeader = event.target;
  318.         if (!hasClass(rowHeader, "memberRowHeader"))
  319.             return;
  320.  
  321.         var row = getAncestorByClass(event.target, "memberRow");
  322.         if (!row)
  323.             return;
  324.  
  325.         var panel = row.parentNode.parentNode.domPanel;
  326.         if (panel)
  327.             panel.breakOnProperty(row);
  328.     }
  329. });
  330.  
  331. const ToolboxPlate = domplate(
  332. {
  333.     tag:
  334.         DIV({"class": "watchToolbox", _domPanel: "$domPanel", onclick: "$onClick"},
  335.             IMG({"class": "watchDeleteButton closeButton", src: "blank.gif"})
  336.         ),
  337.  
  338.     onClick: function(event)
  339.     {
  340.         var toolbox = event.currentTarget;
  341.         toolbox.domPanel.deleteWatch(toolbox.watchRow);
  342.     }
  343. });
  344.  
  345. // ************************************************************************************************
  346.  
  347. Firebug.DOMBasePanel = function() {}
  348.  
  349. Firebug.DOMBasePanel.prototype = extend(Firebug.ActivablePanel,
  350. {
  351.     tag: DirTablePlate.tableTag,
  352.  
  353.     getRealObject: function(object)
  354.     {
  355.         return unwrapObject(object);
  356.     },
  357.  
  358.     rebuild: function(update, scrollTop)
  359.     {
  360.         dispatch([Firebug.A11yModel], 'onBeforeDomUpdateSelection', [this]);
  361.         var members = this.getMembers(this.selection, 0, this.context);
  362.         this.expandMembers(members, this.toggles, 0, 0, this.context);
  363.  
  364.         this.showMembers(members, update, scrollTop);
  365.     },
  366.     /*
  367.      *  @param object a user-level object wrapped in security blanket
  368.      *  @param level for a.b.c, level is 2
  369.      *  @param context
  370.      */
  371.     getMembers: function(object, level, context)
  372.     {
  373.         if (!level)
  374.             level = 0;
  375.  
  376.         var ordinals = [], userProps = [], userClasses = [], userFuncs = [],
  377.             domProps = [], domFuncs = [], domConstants = [];
  378.  
  379.         try
  380.         {
  381.             // Special case for "arguments", which is not enumerable by for...in statement.
  382.             if (isArguments(object))
  383.                 object = cloneArray(object);
  384.  
  385.             var domMembers = getDOMMembers(object);
  386.             var insecureObject = unwrapObject(object);
  387.  
  388.             for (var name in insecureObject)  // enumeration is safe
  389.             {
  390.                 // Ignore only global variables (properties of the |window| object).
  391.                 // javascript.options.strict says ignoreVars is undefined.
  392.                 if (ignoreVars[name] == 1 && (object instanceof Window))
  393.                 {
  394.                     continue;
  395.                 }
  396.  
  397.                 var val;
  398.                 try
  399.                 {
  400.                     val = insecureObject[name];  // getter is safe
  401.                 }
  402.                 catch (exc)
  403.                 {
  404.                     // Sometimes we get exceptions trying to access certain members
  405.                 }
  406.  
  407.                 var ordinal = parseInt(name);
  408.                 if (ordinal || ordinal == 0)
  409.                 {
  410.                     addMember(object, "ordinal", ordinals, name, val, level, 0, context);
  411.                 }
  412.                 else if (typeof(val) == "function")
  413.                 {
  414.                     if (isClassFunction(val))
  415.                         addMember(object, "userClass", userClasses, name, val, level, 0, context);
  416.                     else if (name in domMembers)
  417.                         addMember(object, "domFunction", domFuncs, name, val, level, domMembers[name], context);
  418.                     else
  419.                         addMember(object, "userFunction", userFuncs, name, val, level, 0, context);
  420.                 }
  421.                 else
  422.                 {
  423.                     if (name in domMembers)
  424.                         addMember(object, "dom", domProps, name, val, level, domMembers[name], context);
  425.                     else if (name in domConstantMap)
  426.                         addMember(object, "dom", domConstants, name, val, level, 0, context);
  427.                     else
  428.                         addMember(object, "user", userProps, name, val, level, 0, context);
  429.                 }
  430.             }
  431.         }
  432.         catch (exc)
  433.         {
  434.             // Sometimes we get exceptions just from trying to iterate the members
  435.             // of certain objects, like StorageList, but don't let that gum up the works
  436.             //throw exc;
  437.         }
  438.  
  439.         function sortName(a, b) { return a.name > b.name ? 1 : -1; }
  440.         function sortOrder(a, b) { return a.order > b.order ? 1 : -1; }
  441.  
  442.         var members = [];
  443.  
  444.         members.push.apply(members, ordinals);
  445.  
  446.         if (Firebug.showUserProps)
  447.         {
  448.             userProps.sort(sortName);
  449.             members.push.apply(members, userProps);
  450.         }
  451.  
  452.         if (Firebug.showUserFuncs)
  453.         {
  454.             userClasses.sort(sortName);
  455.             members.push.apply(members, userClasses);
  456.  
  457.             userFuncs.sort(sortName);
  458.             members.push.apply(members, userFuncs);
  459.         }
  460.  
  461.         if (Firebug.showDOMProps)
  462.         {
  463.             domProps.sort(sortName);
  464.             members.push.apply(members, domProps);
  465.         }
  466.  
  467.         if (Firebug.showDOMFuncs)
  468.         {
  469.             domFuncs.sort(sortName);
  470.             members.push.apply(members, domFuncs);
  471.         }
  472.  
  473.         if (Firebug.showDOMConstants)
  474.             members.push.apply(members, domConstants);
  475.  
  476.         return members;
  477.     },
  478.  
  479.     expandMembers: function (members, toggles, offset, level, context)  // recursion starts with offset=0, level=0
  480.     {
  481.         var expanded = 0;
  482.         for (var i = offset; i < members.length; ++i)
  483.         {
  484.             var member = members[i];
  485.             if (member.level > level)
  486.                 break;
  487.  
  488.             if ( toggles.hasOwnProperty(member.name) )
  489.             {
  490.                 member.open = "opened";  // member.level <= level && member.name in toggles.
  491.                 if (member.type == 'string')
  492.                     continue;
  493.                 var newMembers = this.getMembers(member.value, level+1, context);  // sets newMembers.level to level+1
  494.  
  495.                 var args = [i+1, 0];
  496.                 args.push.apply(args, newMembers);
  497.                 members.splice.apply(members, args);
  498.                 expanded += newMembers.length;
  499.                 i += newMembers.length + this.expandMembers(members, toggles[member.name], i+1, level+1, context);
  500.             }
  501.         }
  502.  
  503.         return expanded;
  504.     },
  505.  
  506.     showMembers: function(members, update, scrollTop)
  507.     {
  508.         // If we are still in the midst of inserting rows, cancel all pending
  509.         // insertions here - this is a big speedup when stepping in the debugger
  510.         if (this.timeouts)
  511.         {
  512.             for (var i = 0; i < this.timeouts.length; ++i)
  513.                 this.context.clearTimeout(this.timeouts[i]);
  514.             delete this.timeouts;
  515.         }
  516.  
  517.         if (!members.length)
  518.             return this.showEmptyMembers();
  519.  
  520.         var panelNode = this.panelNode;
  521.         var priorScrollTop = scrollTop == undefined ? panelNode.scrollTop : scrollTop;
  522.  
  523.         // If we are asked to "update" the current view, then build the new table
  524.         // offscreen and swap it in when it's done
  525.         var offscreen = update && panelNode.firstChild;
  526.         var dest = offscreen ? this.document : panelNode;
  527.  
  528.         var table = this.tag.replace({domPanel: this, toggles: this.toggles}, dest);
  529.         var tbody = table.lastChild;
  530.         var rowTag = DirTablePlate.rowTag;
  531.  
  532.         // Insert the first slice immediately
  533.         var setSize = members.length;
  534.         var slice = members.splice(0, insertSliceSize);
  535.         var result = rowTag.insertRows({members: slice}, tbody.lastChild);
  536.         var rowCount = 1;
  537.         var panel = this;
  538.         dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]);
  539.         var timeouts = [];
  540.  
  541.         var delay = 0;
  542.         while (members.length)
  543.         {
  544.             timeouts.push(this.context.setTimeout(function(slice)
  545.             {
  546.                 result = rowTag.insertRows({members: slice}, tbody.lastChild);
  547.                 rowCount += insertSliceSize;
  548.                 dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]);
  549.  
  550.                 if ((panelNode.scrollHeight+panelNode.offsetHeight) >= priorScrollTop)
  551.                     panelNode.scrollTop = priorScrollTop;
  552.             }, delay, members.splice(0, insertSliceSize)));
  553.  
  554.             delay += insertInterval;
  555.         }
  556.  
  557.         if (offscreen)
  558.         {
  559.             timeouts.push(this.context.setTimeout(function()
  560.             {
  561.                 if (panelNode.firstChild)
  562.                     panelNode.replaceChild(table, panelNode.firstChild);
  563.                 else
  564.                     panelNode.appendChild(table);
  565.  
  566.                 // Scroll back to where we were before
  567.                 panelNode.scrollTop = priorScrollTop;
  568.             }, delay));
  569.         }
  570.         else
  571.         {
  572.             timeouts.push(this.context.setTimeout(function()
  573.             {
  574.                 panelNode.scrollTop = scrollTop == undefined ? 0 : scrollTop;
  575.             }, delay));
  576.         }
  577.         this.timeouts = timeouts;
  578.     },
  579.  
  580.     showEmptyMembers: function()
  581.     {
  582.         FirebugReps.Warning.tag.replace({object: "NoMembersWarning"}, this.panelNode);
  583.     },
  584.  
  585.     findPathObject: function(object)
  586.     {
  587.         var pathIndex = -1;
  588.         for (var i = 0; i < this.objectPath.length; ++i)
  589.         {
  590.             if (this.getPathObject(i) == object)
  591.                 return i;
  592.         }
  593.  
  594.         return -1;
  595.     },
  596.  
  597.     getPathObject: function(index)
  598.     {
  599.         var object = this.objectPath[index];
  600.         if (object instanceof Property)
  601.             return object.getObject();
  602.         else
  603.             return object;
  604.     },
  605.  
  606.     getRowObject: function(row)
  607.     {
  608.         var object = getRowOwnerObject(row);
  609.         return object ? object : this.selection;
  610.     },
  611.  
  612.     getRealRowObject: function(row)
  613.     {
  614.         var object = this.getRowObject(row);
  615.         return this.getRealObject(object);
  616.     },
  617.  
  618.     getRowPropertyValue: function(row)
  619.     {
  620.         var object = this.getRealRowObject(row);
  621.         return this.getObjectPropertyValue(object, row.domObject.name);
  622.     },
  623.  
  624.     getObjectPropertyValue: function(object, propName)
  625.     {
  626.         if (!object)
  627.             return;
  628.  
  629.         // Get the value with try-catch statement. This method is used also wihin
  630.         // getContextMenuItems where the exception would break the context menu.
  631.         // 1) The Firebug.Debugger.evaluate can throw
  632.         // 2) object[propName] can also throws in case of e.g. non existing "abc.abc" prop name.
  633.         try
  634.         {
  635.             if (object instanceof jsdIStackFrame)
  636.                 return Firebug.Debugger.evaluate(propName, this.context);
  637.             else
  638.                 return object[propName];
  639.         }
  640.         catch (err)
  641.         {
  642.         }
  643.     },
  644.  
  645.     getRowPathName: function(row)
  646.     {
  647.         var name = row.domObject.name;
  648.         var seperator = "";
  649.  
  650.         if(name.match(/^[\d]+$/))//ordinal
  651.             return ["", "["+name+"]"];
  652.         else if(name.match(rxIdentifier))//identifier
  653.             return [".", name];
  654.         else//map keys
  655.             return ["", "[\""+name.replace(/\\/g, "\\\\").replace(/"/g,"\\\"") + "\"]"];
  656.     },
  657.  
  658.     copyName: function(row)
  659.     {
  660.         var value = this.getRowPathName(row);
  661.         value = value[1];//don't want the seperator
  662.         copyToClipboard(value);
  663.     },
  664.  
  665.     copyPath: function(row)
  666.     {
  667.         var path = this.getPropertyPath(row);
  668.         copyToClipboard(path.join(""));
  669.     },
  670.  
  671.     /*
  672.      * Walk from the current row up to the most ancient parent, building an array.
  673.      * @return array of property names and separators, eg ['foo','.','bar'].
  674.      */
  675.     getPropertyPath: function(row)
  676.     {
  677.         var path = [];
  678.         for(var current = row; current ; current = getParentRow(current))
  679.             path = this.getRowPathName(current).concat(path);
  680.         path.splice(0,1); //don't want the first seperator
  681.         return path;
  682.     },
  683.  
  684.     copyProperty: function(row)
  685.     {
  686.         var value = this.getRowPropertyValue(row);
  687.         copyToClipboard(value);
  688.     },
  689.  
  690.     editProperty: function(row, editValue)
  691.     {
  692.         if (hasClass(row, "watchNewRow"))
  693.         {
  694.             if (this.context.stopped)
  695.                 Firebug.Editor.startEditing(row, "");
  696.             else if (Firebug.Console.isAlwaysEnabled())  // not stopped in debugger, need command line
  697.             {
  698.                 if (Firebug.CommandLine.onCommandLineFocus())
  699.                     Firebug.Editor.startEditing(row, "");
  700.                 else
  701.                     row.innerHTML = $STR("warning.Command line blocked?");
  702.             }
  703.             else
  704.                 row.innerHTML = $STR("warning.Console must be enabled");
  705.         }
  706.         else if (hasClass(row, "watchRow"))
  707.         {
  708.             Firebug.Editor.startEditing(row, getRowName(row));
  709.         }
  710.         else
  711.         {
  712.             var object = this.getRowObject(row);
  713.             this.context.thisValue = object;
  714.  
  715.             if (!editValue)
  716.             {
  717.                 var propValue = this.getRowPropertyValue(row);
  718.  
  719.                 var type = typeof(propValue);
  720.                 if (type == "undefined" || type == "number" || type == "boolean")
  721.                     editValue = propValue;
  722.                 else if (type == "string")
  723.                     editValue = "\"" + escapeJS(propValue) + "\"";
  724.                 else if (propValue == null)
  725.                     editValue = "null";
  726.                 else if (object instanceof Window || object instanceof jsdIStackFrame)
  727.                     editValue = getRowName(row);
  728.                 else
  729.                     editValue = "this." + getRowName(row);
  730.             }
  731.  
  732.             Firebug.Editor.startEditing(row, editValue);
  733.         }
  734.     },
  735.  
  736.     deleteProperty: function(row)
  737.     {
  738.         if (hasClass(row, "watchRow"))
  739.             this.deleteWatch(row);
  740.         else
  741.         {
  742.             var object = getRowOwnerObject(row);
  743.             if (!object)
  744.                 object = this.selection;
  745.             object = this.getRealObject(object);
  746.  
  747.             if (object)
  748.             {
  749.                 var name = getRowName(row);
  750.                 try
  751.                 {
  752.                     delete object[name];
  753.                 }
  754.                 catch (exc)
  755.                 {
  756.                     return;
  757.                 }
  758.  
  759.                 this.rebuild(true);
  760.                 this.markChange();
  761.             }
  762.         }
  763.     },
  764.  
  765.     setPropertyValue: function(row, value)  // value must be string
  766.     {
  767.         var name = getRowName(row);
  768.         if (name == "this")
  769.             return;
  770.  
  771.         var object = this.getRealRowObject(row);
  772.         if (object && !(object instanceof jsdIStackFrame))
  773.         {
  774.              // unwrappedJSObject.property = unwrappedJSObject
  775.              Firebug.CommandLine.evaluate(value, this.context, object, this.context.getGlobalScope(),
  776.                  function success(result, context)
  777.                  {
  778.                      object[name] = result;
  779.                  },
  780.                  function failed(exc, context)
  781.                  {
  782.                      try
  783.                      {
  784.                          object[name] = String(value);  // unwrappedJSobject.property = string
  785.                      }
  786.                      catch (exc)
  787.                      {
  788.                          return;
  789.                      }
  790.                   }
  791.              );
  792.         }
  793.         else if (this.context.stopped)
  794.         {
  795.             try
  796.             {
  797.                 Firebug.CommandLine.evaluate(name+"="+value, this.context);
  798.             }
  799.             catch (exc)
  800.             {
  801.                 try
  802.                 {
  803.                     // See catch block above...
  804.                     object[name] = String(value); // unwrappedJSobject.property = string
  805.                 }
  806.                 catch (exc)
  807.                 {
  808.                     return;
  809.                 }
  810.             }
  811.         }
  812.  
  813.         this.rebuild(true);
  814.         this.markChange();
  815.     },
  816.  
  817.     highlightRow: function(row)
  818.     {
  819.         if (this.highlightedRow)
  820.             cancelClassTimed(this.highlightedRow, "jumpHighlight", this.context);
  821.  
  822.         this.highlightedRow = row;
  823.  
  824.         if (row)
  825.             setClassTimed(row, "jumpHighlight", this.context);
  826.     },
  827.  
  828.     breakOnProperty: function(row)
  829.     {
  830.         var member = row.domObject;
  831.         if (!member)
  832.             return;
  833.  
  834.         // Bail out if this property is not breakable.
  835.         if (!member.breakable)
  836.             return;
  837.  
  838.         //xxxHonza: don't use getRowName to get the prop name. From some reason
  839.         // unwatch doesn't work if row.firstChild.textContent is used.
  840.         // It works only from within the watch handler method if the passed param
  841.         // name is used.
  842.         var name = member.name;
  843.         if (name == "this")
  844.             return;
  845.  
  846.         var object = this.getRowObject(row);
  847.         object = this.getRealObject(object);
  848.         if (!object)
  849.             return;
  850.  
  851.         // Create new or remove an existing breakpoint.
  852.         var breakpoints = this.context.dom.breakpoints;
  853.         var bp = breakpoints.findBreakpoint(object, name);
  854.         if (bp)
  855.         {
  856.             row.removeAttribute("breakpoint");
  857.             breakpoints.removeBreakpoint(object, name);
  858.         }
  859.         else
  860.         {
  861.             breakpoints.addBreakpoint(object, name, this, row);
  862.             row.setAttribute("breakpoint", "true");
  863.         }
  864.     },
  865.  
  866.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  867.     // extends Panel
  868.  
  869.     initialize: function()
  870.     {
  871.         this.objectPath = [];
  872.         this.propertyPath = [];
  873.         this.viewPath = [];
  874.         this.pathIndex = -1;
  875.         this.toggles = {};
  876.  
  877.         Firebug.Panel.initialize.apply(this, arguments);
  878.     },
  879.  
  880.     destroy: function(state)
  881.     {
  882.         var view = this.viewPath[this.pathIndex];
  883.         if (view && this.panelNode.scrollTop)
  884.             view.scrollTop = this.panelNode.scrollTop;
  885.  
  886.         if (this.pathIndex)
  887.             state.pathIndex = this.pathIndex;
  888.         if (this.viewPath)
  889.             state.viewPath = this.viewPath;
  890.         if (this.propertyPath)
  891.             state.propertyPath = this.propertyPath;
  892.  
  893.         if (this.propertyPath.length > 0 && !this.propertyPath[1])
  894.             state.firstSelection = persistObject(this.getPathObject(1), this.context);
  895.  
  896.         Firebug.Panel.destroy.apply(this, arguments);
  897.     },
  898.  
  899.     show: function(state)
  900.     {
  901.         if (!this.selection)
  902.         {
  903.             if (!state)
  904.             {
  905.                 this.select(null);
  906.                 return;
  907.             }
  908.             if (state.viewPath)
  909.                 this.viewPath = state.viewPath;
  910.             if (state.propertyPath)
  911.                 this.propertyPath = state.propertyPath;
  912.  
  913.             var selectObject = defaultObject = this.getDefaultSelection(this.context);
  914.  
  915.             if (state.firstSelection)
  916.             {
  917.                 var restored = state.firstSelection(this.context);
  918.                 if (restored)
  919.                 {
  920.                     selectObject = restored;
  921.                     this.objectPath = [defaultObject, restored];
  922.                 }
  923.                 else
  924.                     this.objectPath = [defaultObject];
  925.             }
  926.             else
  927.                 this.objectPath = [defaultObject];
  928.  
  929.             if (this.propertyPath.length > 1)
  930.                 selectObject = this.resetPaths(selectObject);
  931.  
  932.             var selection = state.pathIndex <= this.objectPath.length-1
  933.                 ? this.getPathObject(state.pathIndex)
  934.                 : this.getPathObject(this.objectPath.length-1);
  935.  
  936.             this.select(selection);
  937.         }
  938.     },
  939.  
  940.     resetPaths: function(selectObject)
  941.     {
  942.         for (var i = 1; i < this.propertyPath.length; ++i)
  943.         {
  944.             var name = this.propertyPath[i];
  945.             if (!name)
  946.                 continue;
  947.  
  948.             var object = selectObject;
  949.             try
  950.             {
  951.                 selectObject = object[name];
  952.             }
  953.             catch (exc)
  954.             {
  955.                 selectObject = null;
  956.             }
  957.  
  958.             if (selectObject)
  959.             {
  960.                 this.objectPath.push(new Property(object, name));
  961.             }
  962.             else
  963.             {
  964.                 // If we can't access a property, just stop
  965.                 this.viewPath.splice(i);
  966.                 this.propertyPath.splice(i);
  967.                 this.objectPath.splice(i);
  968.                 selectObject = this.getPathObject(this.objectPath.length-1);
  969.                 break;
  970.             }
  971.         }
  972.     },
  973.  
  974.     hide: function()
  975.     {
  976.         var view = this.viewPath[this.pathIndex];
  977.         if (view && this.panelNode.scrollTop)
  978.             view.scrollTop = this.panelNode.scrollTop;
  979.     },
  980.  
  981.     getBreakOnNextTooltip: function(enabled)
  982.     {
  983.         return (enabled ? $STR("dom.Disable Break On Property Change") :
  984.             $STR("dom.Break On Property Change"));
  985.     },
  986.  
  987.     supportsObject: function(object)
  988.     {
  989.         if (object == null)
  990.             return 1000;
  991.  
  992.         if (typeof(object) == "undefined")
  993.             return 1000;
  994.         else if (object instanceof SourceLink)
  995.             return 0;
  996.         else
  997.             return 1; // just agree to support everything but not aggressively.
  998.     },
  999.  
  1000.     refresh: function()
  1001.     {
  1002.         this.rebuild(true);
  1003.     },
  1004.  
  1005.     updateSelection: function(object)
  1006.     {
  1007.         var previousIndex = this.pathIndex;
  1008.         var previousView = previousIndex == -1 ? null : this.viewPath[previousIndex];
  1009.  
  1010.         var newPath = this.pathToAppend;
  1011.         delete this.pathToAppend;
  1012.  
  1013.         var pathIndex = this.findPathObject(object);
  1014.         if (newPath || pathIndex == -1)
  1015.         {
  1016.             this.toggles = {};
  1017.  
  1018.             if (newPath)
  1019.             {
  1020.                 // Remove everything after the point where we are inserting, so we
  1021.                 // essentially replace it with the new path
  1022.                 if (previousView)
  1023.                 {
  1024.                     if (this.panelNode.scrollTop)
  1025.                         previousView.scrollTop = this.panelNode.scrollTop;
  1026.  
  1027.                     this.objectPath.splice(previousIndex+1);
  1028.                     this.propertyPath.splice(previousIndex+1);
  1029.                     this.viewPath.splice(previousIndex+1);
  1030.                 }
  1031.  
  1032.                 var value = this.getPathObject(previousIndex);
  1033.                 if (!value)
  1034.                 {
  1035.                     return;
  1036.                 }
  1037.  
  1038.                 for (var i = 0; i < newPath.length; ++i)
  1039.                 {
  1040.                     var name = newPath[i];
  1041.                     var object = value;
  1042.                     try
  1043.                     {
  1044.                         value = value[name];
  1045.                     }
  1046.                     catch(exc)
  1047.                     {
  1048.                         return;
  1049.                     }
  1050.  
  1051.                     ++this.pathIndex;
  1052.                     this.objectPath.push(new Property(object, name));
  1053.                     this.propertyPath.push(name);
  1054.                     this.viewPath.push({toggles: this.toggles, scrollTop: 0});
  1055.                 }
  1056.             }
  1057.             else
  1058.             {
  1059.                 this.toggles = {};
  1060.  
  1061.                 var win = this.context.getGlobalScope();
  1062.                 if (object == win)
  1063.                 {
  1064.                     this.pathIndex = 0;
  1065.                     this.objectPath = [win];
  1066.                     this.propertyPath = [null];
  1067.                     this.viewPath = [{toggles: this.toggles, scrollTop: 0}];
  1068.                 }
  1069.                 else
  1070.                 {
  1071.                     this.pathIndex = 1;
  1072.                     this.objectPath = [win, object];
  1073.                     this.propertyPath = [null, null];
  1074.                     this.viewPath = [
  1075.                         {toggles: {}, scrollTop: 0},
  1076.                         {toggles: this.toggles, scrollTop: 0}
  1077.                     ];
  1078.                 }
  1079.             }
  1080.  
  1081.             this.panelNode.scrollTop = 0;
  1082.             this.rebuild();
  1083.         }
  1084.         else
  1085.         {
  1086.             this.pathIndex = pathIndex;
  1087.  
  1088.             var view = this.viewPath[pathIndex];
  1089.             this.toggles = view ? view.toggles : {};
  1090.  
  1091.             // Persist the current scroll location
  1092.             if (previousView && this.panelNode.scrollTop)
  1093.                 previousView.scrollTop = this.panelNode.scrollTop;
  1094.  
  1095.             this.rebuild(false, view ? view.scrollTop : 0);
  1096.         }
  1097.  
  1098.     },
  1099.  
  1100.     getObjectPath: function(object)
  1101.     {
  1102.         return this.objectPath;
  1103.     },
  1104.  
  1105.     getDefaultSelection: function()
  1106.     {
  1107.         return this.context.getGlobalScope();
  1108.     },
  1109.  
  1110.     updateOption: function(name, value)
  1111.     {
  1112.         const optionMap = {showUserProps: 1, showUserFuncs: 1, showDOMProps: 1,
  1113.             showDOMFuncs: 1, showDOMConstants: 1};
  1114.         if ( optionMap.hasOwnProperty(name) )
  1115.             this.rebuild(true);
  1116.     },
  1117.  
  1118.     getOptionsMenuItems: function()
  1119.     {
  1120.         return [
  1121.             optionMenu("ShowUserProps", "showUserProps"),
  1122.             optionMenu("ShowUserFuncs", "showUserFuncs"),
  1123.             optionMenu("ShowDOMProps", "showDOMProps"),
  1124.             optionMenu("ShowDOMFuncs", "showDOMFuncs"),
  1125.             optionMenu("ShowDOMConstants", "showDOMConstants"),
  1126.             "-",
  1127.             {label: "Refresh", command: bindFixed(this.rebuild, this, true) }
  1128.         ];
  1129.     },
  1130.  
  1131.     getContextMenuItems: function(object, target)
  1132.     {
  1133.         var row = getAncestorByClass(target, "memberRow");
  1134.  
  1135.         var items = [];
  1136.  
  1137.         if (row)
  1138.         {
  1139.             var rowName = getRowName(row);
  1140.             var rowObject = this.getRowObject(row);
  1141.             var rowValue = this.getRowPropertyValue(row);
  1142.  
  1143.             var isWatch = hasClass(row, "watchRow");
  1144.             var isStackFrame = rowObject instanceof jsdIStackFrame;
  1145.  
  1146.             items.push(
  1147.                 "-",
  1148.                 {label: "Copy Name",
  1149.                     command: bindFixed(this.copyName, this, row) },
  1150.                 {label: "Copy Path",
  1151.                     command: bindFixed(this.copyPath, this, row) }
  1152.             );
  1153.  
  1154.             if (typeof(rowValue) == "string" || typeof(rowValue) == "number")
  1155.             {
  1156.                 // Functions already have a copy item in their context menu
  1157.                 items.push(
  1158.                     {label: "CopyValue",
  1159.                         command: bindFixed(this.copyProperty, this, row) }
  1160.                 );
  1161.             }
  1162.  
  1163.             items.push(
  1164.                 "-",
  1165.                 {label: isWatch ? "EditWatch" : (isStackFrame ? "EditVariable" : "EditProperty"),
  1166.                     command: bindFixed(this.editProperty, this, row) }
  1167.             );
  1168.  
  1169.             if (isWatch || (!isStackFrame && !isDOMMember(rowObject, rowName)))
  1170.             {
  1171.                 items.push(
  1172.                     {label: isWatch ? "DeleteWatch" : "DeleteProperty",
  1173.                         command: bindFixed(this.deleteProperty, this, row) }
  1174.                 );
  1175.             }
  1176.  
  1177.             var member = row ? row.domObject : null;
  1178.             if (!isDOMMember(rowObject, rowName) && member && member.breakable)
  1179.             {
  1180.                 items.push(
  1181.                     "-",
  1182.                     {label: "html.dom.label.Break On Property Change", type: "checkbox",
  1183.                         checked: this.context.dom.breakpoints.findBreakpoint(rowObject, rowName),
  1184.                         command: bindFixed(this.breakOnProperty, this, row)}
  1185.                 );
  1186.             }
  1187.         }
  1188.  
  1189.         items.push(
  1190.             "-",
  1191.             {label: "Refresh", command: bindFixed(this.rebuild, this, true) }
  1192.         );
  1193.  
  1194.         return items;
  1195.     },
  1196.  
  1197.     getEditor: function(target, value)
  1198.     {
  1199.         if (!this.editor)
  1200.             this.editor = new DOMEditor(this.document);
  1201.  
  1202.         return this.editor;
  1203.     }
  1204. });
  1205.  
  1206. // ************************************************************************************************
  1207.  
  1208. var DOMMainPanel = Firebug.DOMPanel = function () {};
  1209.  
  1210. Firebug.DOMPanel.DirTable = DirTablePlate;
  1211.  
  1212. DOMMainPanel.prototype = extend(Firebug.DOMBasePanel.prototype,
  1213. {
  1214.     selectRow: function(row, target)
  1215.     {
  1216.         if (!target)
  1217.             target = row.lastChild.firstChild;
  1218.  
  1219.         if (!target || !target.repObject)
  1220.             return;
  1221.  
  1222.         this.pathToAppend = getPath(row);
  1223.  
  1224.         // If the object is inside an array, look up its index
  1225.         var valueBox = row.lastChild.firstChild;
  1226.         if (hasClass(valueBox, "objectBox-array"))
  1227.         {
  1228.             var arrayIndex = FirebugReps.Arr.getItemIndex(target);
  1229.             this.pathToAppend.push(arrayIndex);
  1230.         }
  1231.  
  1232.         // Make sure we get a fresh status path for the object, since otherwise
  1233.         // it might find the object in the existing path and not refresh it
  1234.         Firebug.chrome.clearStatusPath();
  1235.  
  1236.         this.select(target.repObject, true);
  1237.     },
  1238.  
  1239.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1240.  
  1241.     onClick: function(event)
  1242.     {
  1243.         var repNode = Firebug.getRepNode(event.target);
  1244.         if (repNode)
  1245.         {
  1246.             var row = getAncestorByClass(event.target, "memberRow");
  1247.             if (row)
  1248.             {
  1249.                 this.selectRow(row, repNode);
  1250.                 cancelEvent(event);
  1251.             }
  1252.         }
  1253.     },
  1254.  
  1255.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1256.     // extends Panel
  1257.  
  1258.     name: "dom",
  1259.     searchable: true,
  1260.     statusSeparator: ">",
  1261.  
  1262.     initialize: function()
  1263.     {
  1264.         this.onClick = bind(this.onClick, this);
  1265.  
  1266.         Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments);
  1267.     },
  1268.  
  1269.     initializeNode: function(oldPanelNode)
  1270.     {
  1271.         this.panelNode.addEventListener("click", this.onClick, false);
  1272.         dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'console']);
  1273.     },
  1274.  
  1275.     destroyNode: function()
  1276.     {
  1277.         this.panelNode.removeEventListener("click", this.onClick, false);
  1278.         dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'console']);
  1279.     },
  1280.  
  1281.     search: function(text, reverse)
  1282.     {
  1283.         if (!text)
  1284.         {
  1285.             delete this.currentSearch;
  1286.             this.highlightRow(null);
  1287.             return false;
  1288.         }
  1289.  
  1290.         var row;
  1291.         if (this.currentSearch && text == this.currentSearch.text)
  1292.             row = this.currentSearch.findNext(true, undefined, reverse, Firebug.Search.isCaseSensitive(text));
  1293.         else
  1294.         {
  1295.             function findRow(node) { return getAncestorByClass(node, "memberRow"); }
  1296.             this.currentSearch = new TextSearch(this.panelNode, findRow);
  1297.             row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
  1298.         }
  1299.  
  1300.         if (row)
  1301.         {
  1302.             var sel = this.document.defaultView.getSelection();
  1303.             sel.removeAllRanges();
  1304.             sel.addRange(this.currentSearch.range);
  1305.  
  1306.             scrollIntoCenterView(row, this.panelNode);
  1307.  
  1308.             this.highlightRow(row);
  1309.             dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, row]);
  1310.             return true;
  1311.         }
  1312.         else
  1313.         {
  1314.             dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, null]);
  1315.             return false;
  1316.         }
  1317.     }
  1318. });
  1319.  
  1320. // ************************************************************************************************
  1321.  
  1322. function DOMSidePanel() {}
  1323.  
  1324. DOMSidePanel.prototype = extend(Firebug.DOMBasePanel.prototype,
  1325. {
  1326.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1327.     // extends Panel
  1328.  
  1329.     name: "domSide",
  1330.     parentPanel: "html",
  1331.     order: 3,
  1332.  
  1333.     initializeNode: function(oldPanelNode)
  1334.     {
  1335.         dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'console']);
  1336.     },
  1337.  
  1338.     destroyNode: function()
  1339.     {
  1340.         dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'console']);
  1341.     },
  1342. });
  1343.  
  1344. // ************************************************************************************************
  1345.  
  1346. function WatchPanel() {}
  1347.  
  1348. WatchPanel.prototype = extend(Firebug.DOMBasePanel.prototype,
  1349. {
  1350.     tag: DirTablePlate.watchTag,
  1351.  
  1352.     rebuild: function()
  1353.     {
  1354.         this.updateSelection(this.selection);
  1355.     },
  1356.  
  1357.     showEmptyMembers: function()
  1358.     {
  1359.         this.tag.replace({domPanel: this, toggles: {}}, this.panelNode);
  1360.     },
  1361.  
  1362.     addWatch: function(expression)
  1363.     {
  1364.         if (!this.watches)
  1365.             this.watches = [];
  1366.  
  1367.         this.watches.splice(0, 0, expression);
  1368.         this.rebuild(true);
  1369.     },
  1370.  
  1371.     removeWatch: function(expression)
  1372.     {
  1373.         if (!this.watches)
  1374.             return;
  1375.  
  1376.         var index = this.watches.indexOf(expression);
  1377.         if (index != -1)
  1378.             this.watches.splice(index, 1);
  1379.     },
  1380.  
  1381.     editNewWatch: function(value)
  1382.     {
  1383.         var watchNewRow = this.panelNode.getElementsByClassName("watchNewRow").item(0);
  1384.         if (watchNewRow)
  1385.             this.editProperty(watchNewRow, value);
  1386.     },
  1387.  
  1388.     setWatchValue: function(row, value)
  1389.     {
  1390.         var rowIndex = getWatchRowIndex(row);
  1391.         this.watches[rowIndex] = value;
  1392.         this.rebuild(true);
  1393.     },
  1394.  
  1395.     deleteWatch: function(row)
  1396.     {
  1397.         var rowIndex = getWatchRowIndex(row);
  1398.         this.watches.splice(rowIndex, 1);
  1399.         this.rebuild(true);
  1400.  
  1401.         this.context.setTimeout(bindFixed(function()
  1402.         {
  1403.             this.showToolbox(null);
  1404.         }, this));
  1405.     },
  1406.  
  1407.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1408.  
  1409.     showToolbox: function(row)
  1410.     {
  1411.         var toolbox = this.getToolbox();
  1412.         if (row)
  1413.         {
  1414.             if (hasClass(row, "editing"))
  1415.                 return;
  1416.  
  1417.             toolbox.watchRow = row;
  1418.  
  1419.             var offset = getClientOffset(row);
  1420.             toolbox.style.top = offset.y + "px";
  1421.             this.panelNode.appendChild(toolbox);
  1422.         }
  1423.         else
  1424.         {
  1425.             delete toolbox.watchRow;
  1426.             if (toolbox.parentNode)
  1427.                 toolbox.parentNode.removeChild(toolbox);
  1428.         }
  1429.     },
  1430.  
  1431.     getToolbox: function()
  1432.     {
  1433.         if (!this.toolbox)
  1434.             this.toolbox = ToolboxPlate.tag.replace({domPanel: this}, this.document);
  1435.  
  1436.         return this.toolbox;
  1437.     },
  1438.  
  1439.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1440.  
  1441.     onMouseDown: function(event)
  1442.     {
  1443.         var watchNewRow = getAncestorByClass(event.target, "watchNewRow");
  1444.         if (watchNewRow)
  1445.         {
  1446.             this.editProperty(watchNewRow);
  1447.             cancelEvent(event);
  1448.         }
  1449.     },
  1450.  
  1451.     onMouseOver: function(event)
  1452.     {
  1453.         var watchRow = getAncestorByClass(event.target, "watchRow");
  1454.         if (watchRow)
  1455.             this.showToolbox(watchRow);
  1456.     },
  1457.  
  1458.     onMouseOut: function(event)
  1459.     {
  1460.         if (isAncestor(event.relatedTarget, this.getToolbox()))
  1461.             return;
  1462.  
  1463.         var watchRow = getAncestorByClass(event.relatedTarget, "watchRow");
  1464.         if (!watchRow)
  1465.             this.showToolbox(null);
  1466.     },
  1467.  
  1468.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1469.     // extends Panel
  1470.  
  1471.     name: "watches",
  1472.     order: 0,
  1473.     parentPanel: "script",
  1474.  
  1475.     initialize: function()
  1476.     {
  1477.         this.onMouseDown = bind(this.onMouseDown, this);
  1478.         this.onMouseOver = bind(this.onMouseOver, this);
  1479.         this.onMouseOut = bind(this.onMouseOut, this);
  1480.  
  1481.         Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments);
  1482.     },
  1483.  
  1484.     destroy: function(state)
  1485.     {
  1486.         state.watches = this.watches;
  1487.  
  1488.         Firebug.Panel.destroy.apply(this, arguments);
  1489.     },
  1490.  
  1491.     show: function(state)
  1492.     {
  1493.         if (state && state.watches)
  1494.             this.watches = state.watches;
  1495.     },
  1496.  
  1497.     initializeNode: function(oldPanelNode)
  1498.     {
  1499.         this.panelNode.addEventListener("mousedown", this.onMouseDown, false);
  1500.         this.panelNode.addEventListener("mouseover", this.onMouseOver, false);
  1501.         this.panelNode.addEventListener("mouseout", this.onMouseOut, false);
  1502.         dispatch([Firebug.A11yModel], "onInitializeNode", [this, 'console']);
  1503.     },
  1504.  
  1505.     destroyNode: function()
  1506.     {
  1507.         this.panelNode.removeEventListener("mousedown", this.onMouseDown, false);
  1508.         this.panelNode.removeEventListener("mouseover", this.onMouseOver, false);
  1509.         this.panelNode.removeEventListener("mouseout", this.onMouseOut, false);
  1510.         dispatch([Firebug.A11yModel], "onDestroyNode", [this, 'console']);
  1511.     },
  1512.  
  1513.     refresh: function()
  1514.     {
  1515.         this.rebuild(true);
  1516.  
  1517.     },
  1518.  
  1519.     updateSelection: function(object)
  1520.     {
  1521.         dispatch([Firebug.A11yModel], 'onBeforeDomUpdateSelection', [this]);
  1522.         var frame = this.context.currentFrame;
  1523.  
  1524.         var newFrame = frame && frame.isValid && frame.script != this.lastScript;
  1525.         if (newFrame)
  1526.         {
  1527.             this.toggles = {};
  1528.             this.lastScript = frame.script;
  1529.         }
  1530.  
  1531.         var members = [];
  1532.  
  1533.         if (this.watches)
  1534.         {
  1535.             for (var i = 0; i < this.watches.length; ++i)
  1536.             {
  1537.                 var expr = this.watches[i];
  1538.                 var value = null;
  1539.                 Firebug.CommandLine.evaluate(expr, this.context, null, this.context.getGlobalScope(),
  1540.                     function success(result, context)
  1541.                     {
  1542.                         value = result;
  1543.                     },
  1544.                     function failed(result, context)
  1545.                     {
  1546.                         var exc = result;
  1547.                         value = new ErrorCopy(exc+"");
  1548.                     }
  1549.                 );
  1550.  
  1551.                 addMember(object, "watch", members, expr, value, 0);
  1552.             }
  1553.         }
  1554.  
  1555.         if (frame && frame.isValid)
  1556.         {
  1557.             var thisVar = unwrapIValue(frame.thisValue);
  1558.             addMember(object, "user", members, "this", thisVar, 0);
  1559.  
  1560.             var scopeChain = this.generateScopeChain(frame.scope);
  1561.             addMember(object, "scopes", members, "scopeChain", scopeChain, 0);
  1562.  
  1563.             members.push.apply(members, this.getMembers(scopeChain[0], 0, this.context));
  1564.         }
  1565.  
  1566.         this.expandMembers(members, this.toggles, 0, 0, this.context);
  1567.         this.showMembers(members, !newFrame);
  1568.     },
  1569.  
  1570.     generateScopeChain: function (scope)
  1571.     {
  1572.         var ret = [];
  1573.         while (scope) {
  1574.             var scopeVars;
  1575.             // getWrappedValue will not contain any variables for closure
  1576.             // scopes, so we want to special case this to get all variables
  1577.             // in all cases.
  1578.             if (scope.jsClassName == "Call") {
  1579.                 scopeVars = {};
  1580.                 var listValue = {value: null}, lengthValue = {value: 0};
  1581.                 scope.getProperties(listValue, lengthValue);
  1582.  
  1583.                 for (var i = 0; i < lengthValue.value; ++i)
  1584.                 {
  1585.                     var prop = listValue.value[i];
  1586.                     var name = unwrapIValue(prop.name);
  1587.                     if (ignoreVars[name] == 1)
  1588.                     {
  1589.                         continue;
  1590.                     }
  1591.  
  1592.                     scopeVars[name] = unwrapIValue(prop.value);
  1593.                 }
  1594.             } else {
  1595.                 scopeVars = unwrapIValue(scope);
  1596.             }
  1597.  
  1598.             if (scopeVars && scopeVars.hasOwnProperty)
  1599.             {
  1600.                 if (!scopeVars.hasOwnProperty("toString")) {
  1601.                     (function() {
  1602.                         var className = scope.jsClassName;
  1603.                         scopeVars.toString = function() {
  1604.                             return $STR(className + " Scope");
  1605.                         };
  1606.                     })();
  1607.                 }
  1608.  
  1609.                 ret.push(scopeVars);
  1610.             }
  1611.             else
  1612.             {
  1613.             }
  1614.             scope = scope.jsParent;
  1615.         }
  1616.  
  1617.         ret.toString = function() {
  1618.             return $STR("Scope Chain");
  1619.         };
  1620.  
  1621.         return ret;
  1622.     },
  1623.  
  1624. });
  1625.  
  1626. // ************************************************************************************************
  1627. // Local Helpers
  1628.  
  1629. function DOMEditor(doc)
  1630. {
  1631.     this.box = this.tag.replace({}, doc, this);
  1632.     this.input = this.box;
  1633.  
  1634.     this.tabNavigation = false;
  1635.     this.tabCompletion = true;
  1636.     this.completeAsYouType = false;
  1637.     this.fixedWidth = true;
  1638.  
  1639.     this.autoCompleter = Firebug.CommandLine.autoCompleter;
  1640. }
  1641.  
  1642. DOMEditor.prototype = domplate(Firebug.InlineEditor.prototype,
  1643. {
  1644.     tag:
  1645.         INPUT({"class": "fixedWidthEditor a11yFocusNoTab",
  1646.             type: "text", title:$STR("NewWatch"),
  1647.             oninput: "$onInput", onkeypress: "$onKeyPress"}),
  1648.  
  1649.     endEditing: function(target, value, cancel)
  1650.     {
  1651.         // XXXjoe Kind of hackish - fix me
  1652.         delete this.panel.context.thisValue;
  1653.  
  1654.         if (cancel || value == "")
  1655.             return;
  1656.  
  1657.         var row = getAncestorByClass(target, "memberRow");
  1658.         dispatch([Firebug.A11yModel], 'onWatchEndEditing', [this.panel]);
  1659.         if (!row)
  1660.             this.panel.addWatch(value);
  1661.         else if (hasClass(row, "watchRow"))
  1662.             this.panel.setWatchValue(row, value);
  1663.         else
  1664.             this.panel.setPropertyValue(row, value);
  1665.     }
  1666. });
  1667.  
  1668. // ************************************************************************************************
  1669. // Local Helpers
  1670.  
  1671. function isClassFunction(fn)
  1672. {
  1673.     try
  1674.     {
  1675.         for (var name in fn.prototype)
  1676.             return true;
  1677.     } catch (exc) {}
  1678.     return false;
  1679. }
  1680.  
  1681. function isArguments(obj)
  1682. {
  1683.     try
  1684.     {
  1685.         return isFinite(obj.length) && obj.length > 0 && typeof obj.callee === "function";
  1686.     } catch (exc) {}
  1687.     return false;
  1688. }
  1689.  
  1690. function addMember(object, type, props, name, value, level, order, context)
  1691. {
  1692.     var rep = Firebug.getRep(value);    // do this first in case a call to instanceof reveals contents
  1693.     var tag = rep.shortTag ? rep.shortTag : rep.tag;
  1694.  
  1695.     var valueType = typeof(value);
  1696.     var hasChildren = hasProperties(value) && !(value instanceof ErrorCopy) &&
  1697.         (valueType == "function" || (valueType == "object" && value != null)
  1698.         || (valueType == "string" && value.length > Firebug.stringCropLength));
  1699.  
  1700.     // Special case for "arguments", which is not enumerable by for...in statement
  1701.     // and so, hasProperties always returns false.
  1702.     if (!hasChildren && value) // arguments will never be falsy if the arguments exist
  1703.         hasChildren = isArguments(value);
  1704.  
  1705.     var member = {
  1706.         object: object,
  1707.         name: name,
  1708.         value: value,
  1709.         type: type,
  1710.         rowClass: "memberRow-"+type,
  1711.         open: "",
  1712.         order: order,
  1713.         level: level,
  1714.         indent: level*16,
  1715.         hasChildren: hasChildren,
  1716.         tag: tag
  1717.     };
  1718.  
  1719.     // The context doesn't have to be specified (e.g. in case of Watch panel that is based
  1720.     // on the same template as the DOM panel, but doesn't show any breakpoints).
  1721.     if (context)
  1722.     {
  1723.         // xxxHonza: Support for object change not implemented yet.
  1724.         member.breakable = !hasChildren;
  1725.  
  1726.         // xxxHonza: Disable breaking on direct window properties, see #520572
  1727.         if (object instanceof Ci.nsIDOMWindow)
  1728.             member.breakable = false;
  1729.  
  1730.         var breakpoints = context.dom.breakpoints;
  1731.         var bp = breakpoints.findBreakpoint(object, name);
  1732.         if (bp)
  1733.         {
  1734.             member.breakpoint = true;
  1735.             member.disabledBreakpoint = !bp.checked;
  1736.         }
  1737.     }
  1738.  
  1739.     // If the property is implemented using a getter function (and there is no setter
  1740.     // implemented) use a "get" prefix that is displayed in the UI.
  1741.     var o = unwrapObject(object);
  1742.     member.prefix = (o.__lookupGetter__(name) && !o.__lookupSetter__(name)) ? "get " : "";
  1743.  
  1744.     props.push(member);
  1745.     return member;
  1746. }
  1747.  
  1748. function getWatchRowIndex(row)
  1749. {
  1750.     var index = -1;
  1751.     for (; row && hasClass(row, "watchRow"); row = row.previousSibling)
  1752.         ++index;
  1753.     return index;
  1754. }
  1755.  
  1756. function getRowName(row)
  1757. {
  1758.     var labelNode = row.getElementsByClassName("memberLabelCell").item(0);
  1759.     return labelNode.textContent;
  1760. }
  1761.  
  1762. function getRowValue(row)
  1763. {
  1764.     var valueNode = row.getElementsByClassName("memberValueCell").item(0);
  1765.     return valueNode.firstChild.repObject;
  1766. }
  1767.  
  1768. function getRowOwnerObject(row)
  1769. {
  1770.     var parentRow = getParentRow(row);
  1771.     if (parentRow)
  1772.         return getRowValue(parentRow);
  1773. }
  1774.  
  1775. function getParentRow(row)
  1776. {
  1777.     var level = parseInt(row.getAttribute("level"))-1;
  1778.     for (row = row.previousSibling; row; row = row.previousSibling)
  1779.     {
  1780.         if (parseInt(row.getAttribute("level")) == level)
  1781.             return row;
  1782.     }
  1783. }
  1784.  
  1785. function getPath(row)
  1786. {
  1787.     var name = getRowName(row);
  1788.     var path = [name];
  1789.  
  1790.     var level = parseInt(row.getAttribute("level"))-1;
  1791.     for (row = row.previousSibling; row; row = row.previousSibling)
  1792.     {
  1793.         if (parseInt(row.getAttribute("level")) == level)
  1794.         {
  1795.             var name = getRowName(row);
  1796.             path.splice(0, 0, name);
  1797.  
  1798.             --level;
  1799.         }
  1800.     }
  1801.  
  1802.     return path;
  1803. }
  1804.  
  1805. function findRow(parentNode, object)
  1806. {
  1807.     var rows = parentNode.getElementsByClassName("memberRow");
  1808.     for (var i=0; i<rows.length; i++)
  1809.     {
  1810.         var row = rows[i];
  1811.         if (object == row.domObject.object)
  1812.             return row;
  1813.     }
  1814.  
  1815.     return row;
  1816. }
  1817.  
  1818. // ************************************************************************************************
  1819.  
  1820. Firebug.DOMModule.DebuggerListener =
  1821. {
  1822.     getBreakpoints: function(context, groups)
  1823.     {
  1824.         if (!context.dom.breakpoints.isEmpty())
  1825.             groups.push(context.dom.breakpoints);
  1826.     }
  1827. };
  1828.  
  1829. Firebug.DOMModule.BreakpointRep = domplate(Firebug.Rep,
  1830. {
  1831.     inspectable: false,
  1832.  
  1833.     tag:
  1834.         DIV({"class": "breakpointRow focusRow", _repObject: "$bp",
  1835.             role: "option", "aria-checked": "$bp.checked"},
  1836.             DIV({"class": "breakpointBlockHead", onclick: "$onEnable"},
  1837.                 INPUT({"class": "breakpointCheckbox", type: "checkbox",
  1838.                     _checked: "$bp.checked", tabindex : "-1"}),
  1839.                 SPAN({"class": "breakpointName"}, "$bp.propName"),
  1840.                 IMG({"class": "closeButton", src: "blank.gif", onclick: "$onRemove"})
  1841.             ),
  1842.             DIV({"class": "breakpointCode"},
  1843.                 TAG("$bp.object|getObjectTag", {object: "$bp.object"})
  1844.             )
  1845.         ),
  1846.  
  1847.     getObjectTag: function(object)
  1848.     {
  1849.         var rep = Firebug.getRep(object);
  1850.         return rep.shortTag ? rep.shortTag : rep.tag;
  1851.     },
  1852.  
  1853.     onRemove: function(event)
  1854.     {
  1855.         cancelEvent(event);
  1856.  
  1857.         if (!hasClass(event.target, "closeButton"))
  1858.             return;
  1859.  
  1860.         var bpPanel = Firebug.getElementPanel(event.target);
  1861.         var context = bpPanel.context;
  1862.  
  1863.         // Remove from list of breakpoints.
  1864.         var row = getAncestorByClass(event.target, "breakpointRow");
  1865.         var bp = row.repObject;
  1866.         context.dom.breakpoints.removeBreakpoint(bp.object, bp.propName);
  1867.  
  1868.         // Remove from the UI.
  1869.         bpPanel.noRefresh = true;
  1870.         bpPanel.removeRow(row);
  1871.         bpPanel.noRefresh = false;
  1872.  
  1873.         var domPanel = context.getPanel("dom", true);
  1874.         if (domPanel)
  1875.         {
  1876.             var domRow = findRow(domPanel.panelNode, bp.object);
  1877.             if (domRow)
  1878.             {
  1879.                 domRow.removeAttribute("breakpoint");
  1880.                 domRow.removeAttribute("disabledBreakpoint");
  1881.             }
  1882.         }
  1883.     },
  1884.  
  1885.     onEnable: function(event)
  1886.     {
  1887.         var checkBox = event.target;
  1888.         if (!hasClass(checkBox, "breakpointCheckbox"))
  1889.             return;
  1890.  
  1891.         var bpPanel = Firebug.getElementPanel(event.target);
  1892.         var context = bpPanel.context;
  1893.  
  1894.         var bp = getAncestorByClass(checkBox, "breakpointRow").repObject;
  1895.         bp.checked = checkBox.checked;
  1896.  
  1897.         var domPanel = context.getPanel("dom", true);
  1898.         if (domPanel)
  1899.         {
  1900.             var row = findRow(domPanel.panelNode, bp.object);
  1901.             if (row)
  1902.                 row.setAttribute("disabledBreakpoint", bp.checked ? "false" : "true");
  1903.         }
  1904.     },
  1905.  
  1906.     supportsObject: function(object)
  1907.     {
  1908.         return object instanceof Breakpoint;
  1909.     }
  1910. });
  1911.  
  1912. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1913.  
  1914. function Breakpoint(object, propName, objectPath, context)
  1915. {
  1916.     this.context = context;
  1917.     this.propName = propName;
  1918.     this.objectPath = objectPath;
  1919.     this.object = object;
  1920.     this.checked = true;
  1921. }
  1922.  
  1923. Breakpoint.prototype =
  1924. {
  1925.     watchProperty: function()
  1926.     {
  1927.         if (!this.object)
  1928.             return;
  1929.  
  1930.         try
  1931.         {
  1932.             var self = this;
  1933.             this.object.watch(this.propName, function handler(prop, oldval, newval)
  1934.             {
  1935.                 // XXXjjb Beware: in playing with this feature I hit too much recursion multiple times with console.log
  1936.                 // TODO Do something cute in the UI with the error bubble thing
  1937.                 if (self.checked)
  1938.                 {
  1939.                     self.context.breakingCause = {
  1940.                         title: $STR("dom.Break On Property"),
  1941.                         message: cropString(prop, 200),
  1942.                         prevValue: oldval,
  1943.                         newValue: newval
  1944.                     };
  1945.  
  1946.                     Firebug.Breakpoint.breakNow(self.context.getPanel("dom", true));
  1947.                 }
  1948.                 return newval;
  1949.             });
  1950.         }
  1951.         catch (exc)
  1952.         {
  1953.             return false;
  1954.         }
  1955.  
  1956.         return true;
  1957.     },
  1958.  
  1959.     unwatchProperty: function()
  1960.     {
  1961.         if (!this.object)
  1962.             return;
  1963.  
  1964.         try
  1965.         {
  1966.             this.object.unwatch(this.propName);
  1967.         }
  1968.         catch (exc)
  1969.         {
  1970.         }
  1971.     }
  1972. }
  1973.  
  1974. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1975.  
  1976. function DOMBreakpointGroup()
  1977. {
  1978.     this.breakpoints = [];
  1979. }
  1980.  
  1981. DOMBreakpointGroup.prototype = extend(new Firebug.Breakpoint.BreakpointGroup(),
  1982. {
  1983.     name: "domBreakpoints",
  1984.     title: $STR("dom.label.DOM Breakpoints"),
  1985.  
  1986.     addBreakpoint: function(object, propName, panel, row)
  1987.     {
  1988.         var path = panel.getPropertyPath(row);
  1989.         path.pop();
  1990.  
  1991.         // We don't want the last dot.
  1992.         if (path.length > 0 && path[path.length-1] == ".")
  1993.             path.pop();
  1994.  
  1995.         var objectPath = path.join("");
  1996.         var bp = new Breakpoint(object, propName, objectPath, panel.context);
  1997.         if (bp.watchProperty());
  1998.             this.breakpoints.push(bp);
  1999.     },
  2000.  
  2001.     removeBreakpoint: function(object, propName)
  2002.     {
  2003.         var bp = this.findBreakpoint(object, propName);
  2004.         if (bp)
  2005.         {
  2006.             bp.unwatchProperty();
  2007.             remove(this.breakpoints, bp);
  2008.         }
  2009.     },
  2010.  
  2011.     matchBreakpoint: function(bp, args)
  2012.     {
  2013.         var object = args[0];
  2014.         var propName = args[1];
  2015.         return bp.object == object && bp.propName == propName;
  2016.     },
  2017.  
  2018.     // Persistence
  2019.     load: function(context)
  2020.     {
  2021.         var panelState = getPersistedState(context, "dom");
  2022.         if (panelState.breakpoints)
  2023.             this.breakpoints = panelState.breakpoints;
  2024.  
  2025.         this.enumerateBreakpoints(function(bp)
  2026.         {
  2027.             try
  2028.             {
  2029.                 // xxxHonza: Firebug.CommandLine.evaluate should be reused if possible.
  2030.                 // xxxJJB: The Components.utils.evalInSandbox fails from some reason.
  2031.                 var expr = "context.window.wrappedJSObject." + bp.objectPath;
  2032.                 bp.object = eval(expr);
  2033.                 bp.watchProperty();
  2034.  
  2035.             }
  2036.             catch (err)
  2037.             {
  2038.             }
  2039.         });
  2040.     },
  2041.  
  2042.     store: function(context)
  2043.     {
  2044.         this.enumerateBreakpoints(function(bp)
  2045.         {
  2046.             bp.object = null;
  2047.         });
  2048.  
  2049.         var panelState = getPersistedState(context, "dom");
  2050.         panelState.breakpoints = this.breakpoints;
  2051.     },
  2052. });
  2053.  
  2054. // ************************************************************************************************
  2055.  
  2056. Firebug.registerModule(Firebug.DOMModule);
  2057. Firebug.registerPanel(DOMMainPanel);
  2058. Firebug.registerPanel(DOMSidePanel);
  2059. Firebug.registerPanel(WatchPanel);
  2060. Firebug.registerRep(Firebug.DOMModule.BreakpointRep);
  2061.  
  2062. // ************************************************************************************************
  2063.  
  2064. }});
  2065.  
  2066.